let { config } = require('../config.js')(runtime, this)
let singletonRequire = require('../lib/SingletonRequirer.js')(runtime, this)
let commonFunctions = singletonRequire('CommonFunction')
let WidgetUtils = singletonRequire('WidgetUtils')
let FloatyInstance = singletonRequire('FloatyUtil')
let runningQueueDispatcher = singletonRequire('RunningQueueDispatcher')
let automator = singletonRequire('Automator')
let logUtils = singletonRequire('LogUtils')
let signTaskService = singletonRequire('SignTaskService')
let OpenCvUtil = require('../lib/OpenCvUtil.js')
let formatDate = require('../lib/DateUtil.js')
let localOcrUtil = require('../lib/LocalOcrUtil.js')
let storageFactory = singletonRequire('StorageFactory')
let logFloaty = singletonRequire('LogFloaty')
let BaseSignRunner = require('./BaseSignRunner.js')
let WarningFloaty = singletonRequire('WarningFloaty')
let YoloTrainHelper = singletonRequire('YoloTrainHelper')
let YoloDetection = singletonRequire('YoloDetectionUtil')

function BaseSignRunner2 () {
  BaseSignRunner.call(this);
  this.restartLimit = 3

  /**
   * 等待跳过按钮并点击
   */
  this.awaitAndSkip = function (checkingList, useBack) {
    let skip = null
    checkingList = checkingList || ['\\s*允许\\s*', '\\s*跳过\\s*', '\\s*下次再说\\s*']
    checkingList.forEach(checkContent => {
      FloatyInstance.setFloatyText('准备查找是否存在' + checkContent)
      skip = WidgetUtils.widgetGetOne(checkContent, 2000)
      if (skip !== null) {
        if (useBack) {
          automator.back()
        } else {
          automator.clickCenter(skip)
        }
        sleep(1000)
      }
    })
    return skip
  }

  this.getMainTask = function (name) {
    name = name || this.name
    return config.supported_signs.find(task => task.name === name)
  }

  this.getPackage = function () {
    return ''
  }
  this.getDialogText = function () {
    return ''
  }
  this.getTimeout = function () {
    return 3000
  }
  this.clickText = function (text, timeout, region, clickIt, loopCnt) {
    clickIt = clickIt===undefined?true:clickIt
    let element = WidgetUtils.widgetGetOne(text, timeout)
    if (element) {
      clickIt?this.displayButtonAndClick(element, text):0
      return element
    }
    this.pushLog("ocr点 "+text)
    element = this.captureAndCheckByOcr(text, text, region, null, clickIt, loopCnt||1)
    return element
  }

  this.findText = function(text, region, timeout, loopCnt) {
    timeout = timeout || 1000
    let appendFilter = null
    if (region && region.constructor === Array) {
      appendFilter = (matcher) => {
        return matcher.boundsInside(region[0], region[1], region[0]+region[2], region[1]+region[3])
      }
      this.addRectangle(text, region)
    }
    let element = WidgetUtils.widgetGetOne(text, timeout, null, null, null)
    if (element) {
        this.pushLog("找到 "+text)
      return element
    }
    this.pushLog("ocr找 "+text)
    return this.captureAndCheckByOcr(text, text, region, null, false, loopCnt||1)
  }
  this.findObject = function (obj, relation) {
    relation = relation||'or'
    logUtils.debugInfo(['查找目标：{} {}', relation, JSON.stringify(obj)])
    let findCount = 0
    let lst = Object.keys(obj)
    for (let index=0;index<lst.length;++index ) {
      let key = lst[index]
      let value = obj[key]
      if (value.constructor === Object) {
        let count = this.findObject(value, key)
        if (key === 'and' && count === Object.keys(value).length) {
          findCount++
        }
        if (key === 'or' && count > 0) {
          findCount++
        }
      } else if (value.constructor === Array) {
        if (this.findText(key, value, this.getTimeout())) {
          findCount++
        }
      }
      logUtils.debugInfo(['查找结果：{} {}', key, findCount])
      if (relation === 'and' && findCount !== (index+1)) {
        break
      }
      if (relation === 'or' && findCount > 0) {
        break
      }
    }
    return findCount
  }
  this.getTitle = function () {
    let ele = idContains('fb').findOnce()
    if (!ele) {
        ele = WidgetUtils.widgetGetOne('返回', 2000)
        for (let i=0;i<2&&ele;++i) {
          ele = ele.parent()
        }
    }
    let a = 16
    let b = 65
    let c = 1064
    let d = 202
    if (ele) {
        a = ele.bounds().left
        b = ele.bounds().top
        c = ele.bounds().right
        d = ele.bounds().bottom
        a = a>0?a-1:0
        b = b>0?b-1:0
        c = c<device.width?c+1:c
        d = d<device.height?d+1:d
    }
    let lst = textMatches(/.*/).boundsInside(a,b, c,d).find()
    let txt = ''
    if (!lst || lst.length === 0) lst = descMatches(/.*/).boundsInside(a,b, c,d).find()
    lst.forEach(element => {
        if (txt) {
            txt += '/'
        }
        txt += element.text()
    });
    txt = txt || this.captureAndGetOcrText('title', [0, device.height*0.4, device.width, device.height*0.2])
    logUtils.debugInfo(['getTitle {}', txt])
    return txt
  }
  this.backTo = function (backTo, activity) {
    logUtils.debugInfo(['backTo in'])
    const t = new  Date().getTime()
    let element = null
    let cnt = 0
    sleep(this.getTimeout())
    if (this.findText('没有响应', null, this.getTimeout())) {
      this.clickText('确定', this.getTimeout(), null, true)
      sleep(this.getTimeout())
    }
    while (commonFunctions.myCurrentPackage() != this.getPackage() && this.getPackage()) {
      logUtils.debugInfo(['{}当前页面不是 {}双击返回', commonFunctions.myCurrentPackage(), this.getPackage()])
      if (commonFunctions.myCurrentPackage().includes('com.miui.home')) {
        return
      }
      automator.back()
      if (cnt++ > 3) {
        sleep(500)
        automator.back()
      }
      sleep(this.getTimeout())
    }
    let title = this.getTitle()
    let _this = this
    function goBack() {
      _this.pushLog(['返回'])
      automator.back()
      sleep(_this.getTimeout())
      let tmp = _this.getTitle()
      if (((tmp||title) && tmp === title)||(++cnt) > 3) {
        _this.closeDialogByImage()
      }
      title = tmp
    }
    cnt = 0
    let timeout = 60000*2
    let find = false
    while (new Date().getTime() - t < timeout) {
      if (activity && activity !== currentActivity()) {
        logUtils.debugInfo(['activity, 目标：{} 当前：{}', activity, currentActivity()])
        goBack()
        continue
      }
      if (backTo.constructor === String || backTo.constructor === RegExp) {
        element = this.findText(backTo, null, this.getTimeout())
        if (!element) {
          goBack()
          continue
        }
        find = true
        break
      } else if (backTo.constructor === Object) {
        let findCount = this.findObject(backTo)
        if (findCount === 0) {
          goBack()
          continue
        }
        find = true
        break
      }
    }
    logUtils.debugInfo(['backTo结束'])
    if (!find && (new Date().getTime() - t >= timeout || cnt > 10)) {
      this.pushWarningLog('无法退出，重启APP')
      commonFunctions.killCurrentApp()
    }
    return element
  }

  this.findWidget = function(info) {
    let clickMine = null
    while (1) {
      if (info.node) {
        clickMine = info.node
        info.node = null
        if (clickMine) {
          break
        }
      }
      if (info.text) {
        FloatyInstance.setFloatyText(`准备查找 ${info.text}`)
        clickMine = this.findText(info.text, info.region, this.getTimeout())
        if (clickMine && automator.checkCenterClickable(clickMine)) {
          logUtils.debugInfo(['{}[{}]位置:{}', info.text.toString(), (clickMine.text&&clickMine.text())||'', clickMine.bounds()])
          break
        }
        clickMine = null
      } 
      if(info.id) {
        FloatyInstance.setFloatyText(`准备ID查找 ${info.id}`)
        clickMine = WidgetUtils.widgetGetById(info.id, 3000)
        if (clickMine) {
          break
        }
      }
      if (info.notFound) {
        FloatyInstance.setFloatyText(`准备自定义查找`)
        clickMine = info.notFound()
        if (clickMine) {
          break
        }
      }
      FloatyInstance.setFloatyText(`没找到节点`)
      logUtils.debugInfo(['没找到节点'])
      break
    }
    return clickMine;
  }

  /**
   * 根据配置信息找位置、点击、等待元素
   * 
   * @param {Object} info 动作
   */
  this.action = function(info) {
    let retObj = {node:null, waitFor:null}
    let clickMine = this.findWidget(info)
    retObj.node = clickMine
    if (clickMine) {
      try {
        let txt = info.text||info.id||'操作位置'
        logUtils.debugInfo([`{} at {}`, clickMine.text(), JSON.stringify(this.boundsToPosition(clickMine.bounds()))])
        let activity = currentActivity()
        this.displayButtonAndClick(clickMine, txt.toString())
        // this.closeDialogByImage()
        if (info.waitFor) {
          let lst = info.waitFor.forEach?info.waitFor:[info.waitFor]
          let t1 = new Date().getTime()
          while ((new Date().getTime()) - t1 < 20000) {
            for (let txt of lst) {
              if (this.findText(txt, null, this.getTimeout())) {
                t1 = 0
                break
              }
            }
          }
          info.find = t1 === 0
        } else {
          info.find = true
        }
        if (info.sleep) {
          info.sleep(info)
        } else if (info.timeout) {
          sleep(info.timeout)
        }
        if (!info.backTo) {
          return retObj
        }
        this.backTo(info.backTo, activity)
      } catch (err) {
        this.pushWarningLog(`查找 ${info.text} 发生异常`)
        logUtils.debugInfo(['err:{} at line {}', err, err.stack])
        if (clickMine.click) {
          logUtils.debugInfo(['by click'])
          clickMine.click()
          // this.closeDialogByImage()
        }
      }
    }
    return retObj
  }

  
  /**
   * 根据动作序列执行操作
   * 
   * @param {Array} actList 动作序列
   */
  this.pipeLine = function(actList, restarter, restartCount) {
    let value = device.getMusicVolume()
    for (let info of actList) {
      let clickMine = null
      let count = 0
      do {
        count += 1
        let retObj = {}
        if (info.loopFilter) {
          if (this.findText('快速验证', null, this.getTimeout())) {
            automator.back()
            sleep(1000)
          }
          let txt = info.text
          let appendFilter = (matcher) => {
            if (info.appendFilter) {
              return info.appendFilter(matcher)
            }
            if (info.boundsInside) {
              return matcher.boundsInside(info.boundsInside[0],info.boundsInside[1],info.boundsInside[2],info.boundsInside[3],)
            }
            if (info.boundsContains) {
              return matcher.boundsContains(info.boundsContains[0],info.boundsContains[1], info.boundsContains[2], info.boundsContains[3])
            }
            return matcher
          }
          if (info.beforeFind) {
            info.beforeFind()
          }
          let lst = WidgetUtils.widgetGetAll(info.text, device.model=='Redmi Note 5'?10000:5000, null, appendFilter)||[]
          if (lst.length === 0 && info.ocr) {
            logUtils.debugInfo([`widgetGetAll 失败，ocr查找 ${info.text}`])
            let region = info.boundsInside ? [info.boundsInside[0],info.boundsInside[1], 
              info.boundsInside[2]-info.boundsInside[0], info.boundsInside[3]-info.boundsInside[1]] : null
            lst = this.findTextOcr(info.text, info.text, region, this.getTimeout())
          }
          this.pushLog(`${info.text} len=${lst.length}`)
          device.setMusicVolume(0)
          info.loopFilter(lst, info)
          device.setMusicVolume(value)
          clickMine = 1
        } else {
          if (this.findText('快速验证', null, this.getTimeout())) {
            automator.back()
            sleep(1000)
          }
          if (info.beforeFind) {
            info.beforeFind()
          }
          retObj = this.action(info)
          clickMine = retObj.node
          if (clickMine) {
            break
          }
        }
      } while(info.loopFilter && !info.end)
      if (!clickMine) {
        this.pushWarningLog(`act未找到 ${info.text}`)
        logUtils.debugInfo([`point:${info.point}`])
        if (info.end) {
          break
        }
        if (info.point) {
          FloatyInstance.setFloatyInfo(info.point, info.text||info.id)
          automator.click(info.point.x, info.point.y)
          // this.closeDialogByImage()
          sleep(1000)
        }
        restartCount = restartCount||0
        if (++restartCount <= 3) {
          this.pushWarningLog(`未找到 ${info.text} 返回以重开始`)
          if (restarter) {
            return restarter.apply(this, [restartCount])
          }
        }
        break
      }
    }
  }


  this.swipeWidget = function(widget, subx, suby) {
    subx = subx||-300
    suby = suby||-10
    let x = widget.bounds?widget.bounds().centerX():widget[0]
    let y = widget.bounds?widget.bounds().centerY():widget[1]
    let txt = widget.text?widget.text():widget[2]
    x= Math.min(x, config.device_width-1)
    y= Math.min(y, config.device_height-1)
    logUtils.debugInfo(['移动控件：{} [{},{}],[{},{}]', txt, x, y, x+subx, y+suby])
    swipe(x, y, x+subx, y+suby, 600)
    return widget
  }
  function randomHexColor() {
    var hex = Math.floor(Math.random() * 16777216).toString(16);
    while (hex.length < 6) {
    hex = '0' + hex;
    }
    return '#' + hex;
  }
  this.addRectangle = function(txt, region, color) {
    let fltType = 'abc'
    let lst = [region[0], region[1], region[2], region[3]]
    const sub = Math.floor(Math.random() * (11 - 1) + 1)
    if (lst[0] === 0) {
      lst[0] = lst[0] + sub
      lst[3] = lst[3] - sub*2
    }
    color = color || randomHexColor()
    WarningFloaty.addRectangle(txt, lst, color, fltType)
    // setTimeout(() => {
    //   WarningFloaty.clearAll(fltType)
    //   console.log('clearAll', fltType)
    // }, 3000)
    if (this.textThreadA) {
      return
    }
    let that = this
    this.textThreadA = threads.start(function () {
      while (1) {
        sleep(10000)
        WarningFloaty.clearAll(fltType)
      }
      console.log('clearAll', fltType)
      that.textThreadA = null
    })
  } 
  function checkByCenter() {
    left = device.width/2 - 5
    top = device.height/2 - 5
    right = device.width/2 + 5
    bottom = device.height/2 + 5
    let lst = selector().boundsContains(left, top, right, bottom).find()
    lst = lst.filter(item => {
      if (item.childCount() === 0) {
        return true
      }
      return false
    })
    logUtils.debugInfo(['中心点的元素层数{}', lst.length])
  }
  function lonelyIsland(element) {
    if (!element) return true
    let p = element.parent()
    if (!p || p.childCount() <= 1) {
      return true
    }
    let cb = element.bounds();
    let maxSub = 10
    console.log('element', element.boundsInParent())
    let lst = p.children()
    for (let c of lst) {
      if (!c) continue
      console.log(c.boundsInParent())
      if (c === element || c.bounds().equals(element.bounds())) continue
      if (c.bounds().width() > (device.width - 20)) {
        continue
      }
      let nb = c.bounds(); 
      // 水平相邻：在同一行，且右/左间距 ≤ 8px 
      if (Math.abs(cb.top  - nb.top) < maxSub && 
        (Math.abs(cb.right  - nb.left)  <= maxSub || Math.abs(cb.left  - nb.right) <= maxSub)) {
        console.log(`!lonelyIsland=`, c)
          return false
      }
      // 垂直相邻：在同一列，且下/上间距 ≤ 8px 
      else if (Math.abs(cb.left  - nb.left) < maxSub &&
       (Math.abs(cb.bottom  - nb.top) <= maxSub || Math.abs(cb.top - nb.bottom) <= maxSub)) {
        console.log(`!lonelyIsland=`, c)
          return false
      }
      console.log('cc=', c)
    }
    return true
  }
  this.closeDialogByImage = function(txt, type) {
    sleep(1000)
    type = type || 255
    let subs = []
    let sub = 83
    let left = device.width/2 - sub
    let top = device.height*0.5
    let right = device.width/2 + sub
    let bottom = device.height
    let rects = []
    if (type&1) {
      rects.push([left, top, right, bottom*0.9])
      subs.push(165)
    }
    sub = device.model == 'Redmi Note 5' ? 75 : 90
    if (type&2) {
      rects.push([device.width - sub*2, device.height*0.12, device.width, device.height*0.7])
      subs.push(150)
    }
    if (type&4) {
      rects.push([device.width*0.5, device.height*0.2, device.width, device.height*0.5])
      subs.push(150)
    }
    txt = txt || this.getDialogText()
    if ((txt && this.clickText(txt, 2000, [0, device.height*0.3, device.width, device.height*0.4], true))) {
      return
    }
    let node =this.yoloCheck('关闭弹窗', { labelRegex: 'close_icon' }, true)
    if (node) {
      return
    }
    let find = false
    let ndx = -1
    for (let pos of rects) {
      ndx += 1
      this.addRectangle('检测', [pos[0], pos[1],pos[2]-pos[0], pos[3]-pos[1]])
      let lst = selector().visibleToUser().boundsInside(pos[0], pos[1], pos[2], pos[3]).find()
      lst.forEach(element => {
        let rect = element.bounds()
        if (find) return
        // this.addRectangle('test', [rect.left, rect.top, rect.width(), rect.height()])
        if (element.childCount() > 0) {
          logUtils.debugInfo(`filter by childCount ${element.childCount()}`)
          return
        }
        let text = element.text() || element.contentDescription
        if (rect.width() > subs[ndx] || rect.width() < 60) {
          logUtils.debugInfo(`filter by width ${rect.width()}`)
          return
        }
        sub = Math.abs(rect.width() - rect.height())
        if (sub > 10) {
            logUtils.debugInfo(`filter by size ${sub}`)
            return
        }
        if (text && !element.className().includes('Image')) {
          if (!text.includes('关闭')) {
            logUtils.debugInfo(`filter by text ${text} ${element.className()}`)
            return
          }
        }
        if (element.className().includes('Image') && text && /[\u4e00-\u9fa5]/.test(text)) {
          // 含中文内容
          if (!text.includes('关闭')) {
            logUtils.debugInfo(`filter by text ${text} ${element.className()}`)
            return
          }
        }
        if (!lonelyIsland(element)) {
          logUtils.debugInfo(`filter by lonelyIsland ${element}`)
          return
        }
        console.log(text, 'close=', element)
        // YoloTrainHelper.saveImageWithLabels(
        //   commonFunctions.captureScreen(), '签到页面', 
        //   [{name:'关闭按钮', shape: 'rectangle', region:[rct.left,rct.top,rct.width(),rct.height()]}], 
        //   'jd', true
        // )
        this.addRectangle('关闭', [rect.left, rect.top, rect.width(), rect.height()])
        let p = element
        for (let i=0;i<3&&p;++i) {
          p = p.parent()
          console.log('parent=', p?p.bounds():null)
          find = true
        }
        if (!find) {
          logUtils.debugInfo(['过滤掉了'])
          return
        }
        img = images.clip(commonFunctions.captureScreen(), 
          element.bounds().left, element.bounds().top, element.bounds().width(), element.bounds().height())
        console.log('img=', images.toBase64(img))
        this.displayButtonAndClick(element, 'close button')
      });
      if (find) break
    }
    if (!find)
          logUtils.debugInfo(['没有找到弹窗'])
    return find
  }
  this.closeDialog = function(regx, timeout, x, y) {
      timeout = timeout || 2000
      const times = 40
      cnt = times
      x= x || (device.width-80)
      y= y || 100
      let sub = 50
      while (cnt--) {
          let ele = this.findText(regx, null, timeout)
          if (ele) {
              this.displayButtonPoint({x,y}, 'X')
              automator.click(x, y)
              if (y + sub > device.height) {
                  sub = 0-sub
              }
              if (y + sub < 0) {
                  break
              }
              y+=sub
              sleep(500)
              if (cnt ==0 && this.findText(regx, null, timeout)) {
                  cnt = times
                  y = 100
              }
              continue
          }
          break
      }
      return cnt < times-1
  }
  this.displayButtonPoint = function (point, desc, delay) {
    if (point) {
      FloatyInstance.setFloatyInfo(
        {
          x: point.x,
          y: point.y
        },
        desc||'point')
      sleep(delay || 1000)
    }
    return point
  }
  this.captureAndCheckByOcr = function (regex, content, region, delay, clickIt, loop) {
    let options = {}
    if (typeof content === 'object' && content != null) {
      options = content || {}
      options.loop = loop
    } else {
      options.content = content || regex
      options.region = region || null
      options.delay = delay || 800
      options.clickIt = clickIt || false
      options.loop = loop === undefined ? 3 : loop
    }
    if (!localOcrUtil.enabled) {
      logUtils.warnInfo('当前AutoJS不支持OCR')
      this.pushLog('当前AutoJS不支持OCR')
      return null
    }
    if (typeof options.loop === 'undefined') {
      options.loop = 3
    }
    content = options.content || regex
    region = options.region || null
    delay = options.delay || 800
    clickIt = options.clickIt || false
    loop = options.loop
    FloatyInstance.hide()
    logFloaty.hide()
    sleep(40)
    let screen = commonFunctions.captureScreen()
    FloatyInstance.restore()
    logFloaty.show()
    logUtils.debugInfo('准备OCR查找目标：' + content + ' 正则：' + regex)
    if (screen) {
      let findText = localOcrUtil.recognizeWithBounds(screen, region, regex)
      if (findText && findText.length > 0) {
        let collect = findText[0].bounds
        logUtils.debugInfo(['OCR找到了目标 [{}]: {}', content, findText[0].label])
        FloatyInstance.setFloatyInfo({
          x: collect.centerX(),
          y: collect.centerY()
        }, '找到了 ' + content)
        sleep(delay)
        if (clickIt) {
          automator.click(collect.centerX(), collect.centerY())
          sleep(delay)
        }
        return this.wrapOcrPointWithBounds(collect, findText[0].label)
      } else if (loop-- > 1) {
        sleep(delay)
        this.pushLog('未找到目标：' + content + ' 剩余次数：' + loop)
        logUtils.debugInfo(['未找到目标「{}」进行下一次查找，剩余尝试次数：{}', content, loop])
        logUtils.debugForDev(['图片数据：[data:image/png;base64,{}]', images.toBase64(images.clip(screen, config.device_width / 2, config.device_height * 0.7, config.device_width - config.device_width / 2, config.device_height - config.device_height * 0.7))])
        return this.captureAndCheckByOcr(regex, content, region, delay, clickIt, loop)
      }
    } else {
      logUtils.errorInfo('截图失败')
    }
    FloatyInstance.setFloatyText('ocr未找到 ' + content)
    sleep(delay)
    return null
  }
  this.findTextOcr = function (regex, content, region) { 
    FloatyInstance.hide()
    logFloaty.hide()
    sleep(40)
    let screen = commonFunctions.captureScreen()
    FloatyInstance.restore()
    logFloaty.show()
    logUtils.debugInfo('准备OCR查找目标：' + content + ' 正则：' + regex)
    if (screen) {
      let findText = localOcrUtil.recognizeWithBounds(screen, region, regex)
      let lst = []
      for (let item of findText) {
        lst.push(this.wrapOcrPointWithBounds(item.bounds, item.label))
      }
      return lst
    } else {
      logUtils.errorInfo('截图失败')
    }
  }
  this.wrapOcrPointWithBounds = function (ocrPoint, label) {
    if (!ocrPoint) {
      return null
    }
    if (!ocrPoint.bounds) {
      let newPoint = Object.create(ocrPoint)
      newPoint.bounds = () => ocrPoint
      newPoint.text = () => label
      return newPoint
    }
    return ocrPoint
  }  
  this.allText = function(ele, loop) {
    if (!ele || !ele.text) {
        return ''
    }
    let txt = ele.text() || ele.desc() || ''
    ele.children().forEach(item => {
      let b = this.allText(item, 1)
      if (b) {
          if (txt) txt += ' '
          txt += b
      }
    })
    txt = (!loop && txt) ? txt + '\n':txt
    return txt
  }
  /**
   * yolo查找所有匹配的对象
   * @param {string} desc 描述信息
   * @param {object} filter 过滤配置 可信度 label 等等
   * @return {array}
   */
  this.yoloCheckAll = function (desc, filter) {
    let img = null
    let results = []
    let tryTime = 5
    WarningFloaty.clearAll()
    logUtils.debugInfo(['通过YOLO查找：{} props: {}', desc, JSON.stringify(filter)])
    do {
      sleep(400)
      img = commonFunctions.captureScreen()
      results = YoloDetection.forward(img, filter) || []
    } while (results.length <= 0 && tryTime-- > 0)
    if (results.length > 0) {
      img = commonFunctions.captureScreen()
      return results.map(result => {
        let { x, y, width, height, label, confidence } = result
        let left = x, top = y
        WarningFloaty.addRectangle('找到：' + desc, [left, top, width, height])
        logUtils.debugInfo(['通过YOLO找到目标：{} label: {} confidence: {}', desc, label, confidence])
        if (confidence < 0.9) {
          YoloTrainHelper.saveImage(img, desc + 'yolo准确率低', 'low_predict', config.yolo_save_low_predict)
        }
        return { x: left + width / 2, y: top + height / 2, width: width, height: height, left: left, top: top, label: label }
      })
    } else {
      logUtils.debugInfo(['未能通过YOLO找到：{}', desc])
      YoloTrainHelper.saveImage(img, 'yolo查找失败' + desc, config.yolo_save_check_failed)
    }
    return null
  }

  this.yoloCheck = function (desc, filter, click) {
    try {
      let results = this.yoloCheckAll(desc, filter)
      if (results && results.length > 0) {
        let node = results[0]
        if (click) {
          // this.addRectangle('yolo close', [node.top, node.left, node.width, node.height])
          automator.click(node.x, node.y)
          sleep(1000)
        }
        return node
      }
    } catch (e) {
      logUtils.errorInfo(e)
    }
    return null
  }

  /**
   * abstract funcs
   */
  this.exec = () => { }

  this.notifyOper = function() {
    let store = this.createStoreOperator('notify_token', { pushPlusToken: '', wxpusherToken: '', channel: '',
      logSite: '', logToken:'', logDir: ''
     }, true)
    if (!this.notify) {
      this.notify = new NotifyOper(this, store, 'unisign')
    }
    return this.notify
  }
  function NotifyOper(_this, tokenStore, taskName) {
    this.notifyMsg = ''
    this.storeKey = `${taskName}_notifyMsg`
    this.tokenStore = tokenStore
    this.storage = storages.create(`${taskName}_group`);
    this.appendNotifyMsg = function(ele, allChild) {
      allChild = allChild===undefined ? true:allChild
      let obj = this.storageOp(this.storeKey, false)
      if (!this.notifyMsg && obj && obj.msg) {
        this.sendNotifyMsg('上次任务进展', obj.msg)
      }
      if (allChild) {
          this.notifyMsg += _this.allText(ele)
      } else {
        let txt = ele.text() || ''
        ele.children().forEach(item => {
            let b = item.text()
            if (b) {
                if (txt) txt += ' '
                txt += b
            }
        })
        if (txt) txt += '\n';
        this.notifyMsg += txt
      }
      this.storageOp(this.storeKey, true, { msg: this.notifyMsg })
    }
    this.appendMsg = function(msg) {
      let obj = this.storageOp(this.storeKey, false)
      if (!this.notifyMsg && obj && obj.msg) {
        this.sendNotifyMsg('上次任务进展', obj.msg)
      }
      this.notifyMsg += msg
    }
    this.storageOp = function(k, isPut, v) {
      if (isPut) {
        this.storage.put(k, JSON.stringify(v))
      } else {
        let s = this.storage.get(k,v)
        if (s) {
            return JSON.parse(s)
        }
        return s
      }
    }
    this.wxpusher = function(title, content, token) {
        let url = `https://wxpusher.zjiecode.com/api/send/message`
        let data = {
            appToken: token.split('/')[0],
            content,
            summary: title,
            contentType: 1,
            uids: [token.split('/')[1]],
            verifyPay: false,
            verifyPayType: 0
        }
        const options = {
            headers: {
            'Content-Type': 'application/json',
            }
        };
        let rsp = http.postJson(url, data, options)
        console.log(rsp.body.string())

    }
    this.pushplus = function(title, content, token) {
        let url = `http://www.pushplus.plus/send`
        let data = {
            title,
            content,
            token
        }
        const options = {
            headers: {
            'Content-Type': 'application/json',
            }
        };
        let rsp = http.postJson(url, data, options)
        console.log(rsp.body.string())
    }
    this.sendNotifyMsg = function(title, content) {
      if (tokenStore.getValue().channel === 'wxpusher') {
        this.wxpusher(title, content||this.notifyMsg, tokenStore.getValue().wxpusherToken)
      }
      if (tokenStore.getValue().channel === 'pushplus') {
        this.pushplus(title, content||this.notifyMsg, tokenStore.getValue().pushPlusToken)
      }
      if (!content) {
        try{
          this.sendLog()
        } catch(e) {          
          commonFunctions.printExceptionStack(e)
        }
        this.storageOp('notifyMsg', true, { msg: '' })
        launch('com.termux')
      }
    }
    this.sendLog = function (localPath) {
      const SERVER = tokenStore.getValue().logSite; 
      if (!SERVER) return
      const TOKEN  = tokenStore.getValue().logToken;
      const REMOTE_DIR = tokenStore.getValue().logDir;
      let LOCAL_FILE = localPath || files.cwd().split('/脚本/')[0] + '/脚本/.logs/autojs-log4j.txt'
      let filename = files.getName(LOCAL_FILE)
      let bak = LOCAL_FILE+'.bak'
      files.copy(LOCAL_FILE, bak)
      LOCAL_FILE = bak
      const mimeType = "application/octet-stream"; // 或根据 files.getMimeType  动态获取       
      const putUrl = `${SERVER}/api/fs/put`;      // 直传接口 
      importPackage(Packages["okhttp3"])
      importClass(java.io.File)
      importClass(java.util.concurrent.TimeUnit); 
      const file = new File(LOCAL_FILE);
      const mediaType = MediaType.parse("application/octet-stream");  // 二进制即可 
      const requestBody = RequestBody.create(mediaType,  file);
      let request = new Request.Builder()
        .url(putUrl)
        .put(requestBody)
      request.addHeader('authorization', TOKEN)
      request.addHeader('File-Path', encodeURIComponent(REMOTE_DIR + "/" + filename))
      request.addHeader('Content-Type', mimeType)
      request.addHeader('Content-Length', file.length())
      request.addHeader('Overwrite', 'true')
      console.log(file.length(), request.toString())
      request = request.build()
      let response = null
      let result = null
      try {
        let okHttpClient = new OkHttpClient()
        response = okHttpClient.newCall(request).execute()
        if (response != null && response.body() != null) {
          let resultString = response.body().string()
          logUtils.debugInfo(['请求结果：{}', resultString])
        }
      } catch (e) {
        commonFunctions.printExceptionStack(e)
      } finally {
        files.remove(LOCAL_FILE)
        if (response !== null) {
          response.close()
        }
      }
    }
  }
}
/**
 * 初始化自定义存储，用于每日数据缓存
 */
BaseSignRunner2.prototype.initStorages = () => { }
BaseSignRunner2.prototype.constructor = BaseSignRunner2
module.exports = BaseSignRunner2